// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/child/v8_value_converter_impl.h"

#include <stddef.h>
#include <stdint.h>

#include <cmath>
#include <memory>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "v8/include/v8.h"

namespace content {

// To improve the performance of
// V8ValueConverterImpl::UpdateAndCheckUniqueness, identity hashes of objects
// are used during checking for duplicates. For testing purposes we need to
// ignore the hash sometimes. Create this helper object to avoid using identity
// hashes for the lifetime of the helper.
class ScopedAvoidIdentityHashForTesting {
public:
    // The hashes will be ignored in |converter|, which must not be NULL and it
    // must outlive the created instance of this helper.
    explicit ScopedAvoidIdentityHashForTesting(
        content::V8ValueConverterImpl* converter);
    ~ScopedAvoidIdentityHashForTesting();

private:
    content::V8ValueConverterImpl* converter_;

    DISALLOW_COPY_AND_ASSIGN(ScopedAvoidIdentityHashForTesting);
};

ScopedAvoidIdentityHashForTesting::ScopedAvoidIdentityHashForTesting(
    content::V8ValueConverterImpl* converter)
    : converter_(converter)
{
    CHECK(converter_);
    converter_->avoid_identity_hash_for_testing_ = true;
}

ScopedAvoidIdentityHashForTesting::~ScopedAvoidIdentityHashForTesting()
{
    converter_->avoid_identity_hash_for_testing_ = false;
}

class V8ValueConverterImplTest : public testing::Test {
public:
    V8ValueConverterImplTest()
        : isolate_(v8::Isolate::GetCurrent())
    {
    }

protected:
    void SetUp() override
    {
        v8::HandleScope handle_scope(isolate_);
        v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
        context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
    }

    void TearDown() override { context_.Reset(); }

    std::string GetString(base::DictionaryValue* value, const std::string& key)
    {
        std::string temp;
        if (!value->GetString(key, &temp)) {
            ADD_FAILURE();
            return std::string();
        }
        return temp;
    }

    std::string GetString(v8::Local<v8::Object> value, const std::string& key)
    {
        v8::Local<v8::String> temp = value->Get(v8::String::NewFromUtf8(isolate_, key.c_str()))
                                         .As<v8::String>();
        if (temp.IsEmpty()) {
            ADD_FAILURE();
            return std::string();
        }
        v8::String::Utf8Value utf8(temp);
        return std::string(*utf8, utf8.length());
    }

    std::string GetString(base::ListValue* value, uint32_t index)
    {
        std::string temp;
        if (!value->GetString(static_cast<size_t>(index), &temp)) {
            ADD_FAILURE();
            return std::string();
        }
        return temp;
    }

    std::string GetString(v8::Local<v8::Array> value, uint32_t index)
    {
        v8::Local<v8::String> temp = value->Get(index).As<v8::String>();
        if (temp.IsEmpty()) {
            ADD_FAILURE();
            return std::string();
        }
        v8::String::Utf8Value utf8(temp);
        return std::string(*utf8, utf8.length());
    }

    int32_t GetInt(v8::Local<v8::Object> value, const std::string& key)
    {
        v8::Local<v8::Int32> temp = value->Get(v8::String::NewFromUtf8(isolate_, key.c_str()))
                                        .As<v8::Int32>();
        if (temp.IsEmpty()) {
            ADD_FAILURE();
            return -1;
        }
        return temp->Value();
    }

    int32_t GetInt(v8::Local<v8::Object> value, uint32_t index)
    {
        v8::Local<v8::Int32> temp = value->Get(index).As<v8::Int32>();
        if (temp.IsEmpty()) {
            ADD_FAILURE();
            return -1;
        }
        return temp->Value();
    }

    bool IsNull(base::DictionaryValue* value, const std::string& key)
    {
        base::Value* child = NULL;
        if (!value->Get(key, &child)) {
            ADD_FAILURE();
            return false;
        }
        return child->GetType() == base::Value::Type::NONE;
    }

    bool IsNull(v8::Local<v8::Object> value, const std::string& key)
    {
        v8::Local<v8::Value> child = value->Get(v8::String::NewFromUtf8(isolate_, key.c_str()));
        if (child.IsEmpty()) {
            ADD_FAILURE();
            return false;
        }
        return child->IsNull();
    }

    bool IsNull(base::ListValue* value, uint32_t index)
    {
        base::Value* child = NULL;
        if (!value->Get(static_cast<size_t>(index), &child)) {
            ADD_FAILURE();
            return false;
        }
        return child->GetType() == base::Value::Type::NONE;
    }

    bool IsNull(v8::Local<v8::Array> value, uint32_t index)
    {
        v8::Local<v8::Value> child = value->Get(index);
        if (child.IsEmpty()) {
            ADD_FAILURE();
            return false;
        }
        return child->IsNull();
    }

    void TestWeirdType(const V8ValueConverterImpl& converter,
        v8::Local<v8::Value> val,
        base::Value::Type expected_type,
        std::unique_ptr<base::Value> expected_value)
    {
        v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
        std::unique_ptr<base::Value> raw(converter.FromV8Value(val, context));

        if (expected_value) {
            ASSERT_TRUE(raw.get());
            EXPECT_TRUE(expected_value->Equals(raw.get()));
            EXPECT_EQ(expected_type, raw->GetType());
        } else {
            EXPECT_FALSE(raw.get());
        }

        v8::Local<v8::Object> object(v8::Object::New(isolate_));
        object->Set(v8::String::NewFromUtf8(isolate_, "test"), val);
        std::unique_ptr<base::DictionaryValue> dictionary(
            base::DictionaryValue::From(converter.FromV8Value(object, context)));
        ASSERT_TRUE(dictionary.get());

        if (expected_value) {
            base::Value* temp = NULL;
            ASSERT_TRUE(dictionary->Get("test", &temp));
            EXPECT_EQ(expected_type, temp->GetType());
            EXPECT_TRUE(expected_value->Equals(temp));
        } else {
            EXPECT_FALSE(dictionary->HasKey("test"));
        }

        v8::Local<v8::Array> array(v8::Array::New(isolate_));
        array->Set(0, val);
        std::unique_ptr<base::ListValue> list(
            base::ListValue::From(converter.FromV8Value(array, context)));
        ASSERT_TRUE(list.get());
        if (expected_value) {
            base::Value* temp = NULL;
            ASSERT_TRUE(list->Get(0, &temp));
            EXPECT_EQ(expected_type, temp->GetType());
            EXPECT_TRUE(expected_value->Equals(temp));
        } else {
            // Arrays should preserve their length, and convert unconvertible
            // types into null.
            base::Value* temp = NULL;
            ASSERT_TRUE(list->Get(0, &temp));
            EXPECT_EQ(base::Value::Type::NONE, temp->GetType());
        }
    }

    v8::Isolate* isolate_;

    // Context for the JavaScript in the test.
    v8::Persistent<v8::Context> context_;
};

TEST_F(V8ValueConverterImplTest, BasicRoundTrip)
{
    std::unique_ptr<base::Value> original_root = base::test::ParseJson(
        "{ \n"
        "  \"null\": null, \n"
        "  \"true\": true, \n"
        "  \"false\": false, \n"
        "  \"positive-int\": 42, \n"
        "  \"negative-int\": -42, \n"
        "  \"zero\": 0, \n"
        "  \"double\": 88.8, \n"
        "  \"big-integral-double\": 9007199254740992.0, \n" // 2.0^53
        "  \"string\": \"foobar\", \n"
        "  \"empty-string\": \"\", \n"
        "  \"dictionary\": { \n"
        "    \"foo\": \"bar\",\n"
        "    \"hot\": \"dog\",\n"
        "  }, \n"
        "  \"empty-dictionary\": {}, \n"
        "  \"list\": [ \"bar\", \"foo\" ], \n"
        "  \"empty-list\": [], \n"
        "}");

    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    V8ValueConverterImpl converter;
    v8::Local<v8::Object> v8_object = converter.ToV8Value(original_root.get(), context).As<v8::Object>();
    ASSERT_FALSE(v8_object.IsEmpty());

    EXPECT_EQ(static_cast<const base::DictionaryValue&>(*original_root).size(),
        v8_object->GetPropertyNames()->Length());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "null"))->IsNull());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "true"))->IsTrue());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "false"))->IsFalse());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "positive-int"))
                    ->IsInt32());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "negative-int"))
                    ->IsInt32());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "zero"))->IsInt32());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "double"))->IsNumber());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(
                                   isolate_, "big-integral-double"))
                    ->IsNumber());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "string"))->IsString());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty-string"))
                    ->IsString());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "dictionary"))
                    ->IsObject());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(
                                   isolate_, "empty-dictionary"))
                    ->IsObject());
    EXPECT_TRUE(
        v8_object->Get(v8::String::NewFromUtf8(isolate_, "list"))->IsArray());
    EXPECT_TRUE(v8_object->Get(v8::String::NewFromUtf8(isolate_, "empty-list"))
                    ->IsArray());

    std::unique_ptr<base::Value> new_root(
        converter.FromV8Value(v8_object, context));
    EXPECT_NE(original_root.get(), new_root.get());
    EXPECT_TRUE(original_root->Equals(new_root.get()));
}

TEST_F(V8ValueConverterImplTest, KeysWithDots)
{
    std::unique_ptr<base::Value> original = base::test::ParseJson("{ \"foo.bar\": \"baz\" }");

    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    V8ValueConverterImpl converter;
    std::unique_ptr<base::Value> copy(converter.FromV8Value(
        converter.ToV8Value(original.get(), context), context));

    EXPECT_TRUE(original->Equals(copy.get()));
}

TEST_F(V8ValueConverterImplTest, ObjectExceptions)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    // Set up objects to throw when reading or writing 'foo'.
    const char* source = "Object.prototype.__defineSetter__('foo', "
                         "    function() { throw new Error('muah!'); });"
                         "Object.prototype.__defineGetter__('foo', "
                         "    function() { throw new Error('muah!'); });";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    script->Run();

    v8::Local<v8::Object> object(v8::Object::New(isolate_));
    object->Set(v8::String::NewFromUtf8(isolate_, "bar"),
        v8::String::NewFromUtf8(isolate_, "bar"));

    // Converting from v8 value should replace the foo property with null.
    V8ValueConverterImpl converter;
    std::unique_ptr<base::DictionaryValue> converted(
        base::DictionaryValue::From(converter.FromV8Value(object, context)));
    EXPECT_TRUE(converted.get());
    // http://code.google.com/p/v8/issues/detail?id=1342
    // EXPECT_EQ(2u, converted->size());
    // EXPECT_TRUE(IsNull(converted.get(), "foo"));
    EXPECT_EQ(1u, converted->size());
    EXPECT_EQ("bar", GetString(converted.get(), "bar"));

    // Converting to v8 value should not trigger the setter.
    converted->SetString("foo", "foo");
    v8::Local<v8::Object> copy = converter.ToV8Value(converted.get(), context).As<v8::Object>();
    EXPECT_FALSE(copy.IsEmpty());
    EXPECT_EQ(2u, copy->GetPropertyNames()->Length());
    EXPECT_EQ("foo", GetString(copy, "foo"));
    EXPECT_EQ("bar", GetString(copy, "bar"));
}

TEST_F(V8ValueConverterImplTest, ArrayExceptions)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "(function() {"
                         "var arr = [];"
                         "arr.__defineSetter__(0, "
                         "    function() { throw new Error('muah!'); });"
                         "arr.__defineGetter__(0, "
                         "    function() { throw new Error('muah!'); });"
                         "arr[1] = 'bar';"
                         "return arr;"
                         "})();";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Array> array = script->Run().As<v8::Array>();
    ASSERT_FALSE(array.IsEmpty());

    // Converting from v8 value should replace the first item with null.
    V8ValueConverterImpl converter;
    std::unique_ptr<base::ListValue> converted(
        base::ListValue::From(converter.FromV8Value(array, context)));
    ASSERT_TRUE(converted.get());
    // http://code.google.com/p/v8/issues/detail?id=1342
    EXPECT_EQ(2u, converted->GetSize());
    EXPECT_TRUE(IsNull(converted.get(), 0));

    // Converting to v8 value should not be affected by the getter/setter
    // because the setters/getters are defined on the array instance, not
    // on the Array's prototype.
    converted.reset(static_cast<base::ListValue*>(
        base::test::ParseJson("[ \"foo\", \"bar\" ]").release()));
    v8::Local<v8::Array> copy = converter.ToV8Value(converted.get(), context).As<v8::Array>();
    ASSERT_FALSE(copy.IsEmpty());
    EXPECT_EQ(2u, copy->Length());
    EXPECT_EQ("foo", GetString(copy, 0));
    EXPECT_EQ("bar", GetString(copy, 1));
}

TEST_F(V8ValueConverterImplTest, WeirdTypes)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    v8::Local<v8::RegExp> regex(v8::RegExp::New(
        v8::String::NewFromUtf8(isolate_, "."), v8::RegExp::kNone));

    V8ValueConverterImpl converter;
    TestWeirdType(converter, v8::Undefined(isolate_),
        base::Value::Type::NONE, // Arbitrary type, result is NULL.
        std::unique_ptr<base::Value>());
    TestWeirdType(converter, v8::Date::New(isolate_, 1000),
        base::Value::Type::DICTIONARY,
        std::unique_ptr<base::Value>(new base::DictionaryValue()));
    TestWeirdType(converter, regex, base::Value::Type::DICTIONARY,
        std::unique_ptr<base::Value>(new base::DictionaryValue()));

    converter.SetDateAllowed(true);
    TestWeirdType(converter, v8::Date::New(isolate_, 1000),
        base::Value::Type::DOUBLE,
        std::unique_ptr<base::Value>(new base::FundamentalValue(1.0)));

    converter.SetRegExpAllowed(true);
    TestWeirdType(converter, regex, base::Value::Type::STRING,
        std::unique_ptr<base::Value>(new base::StringValue("/./")));
}

TEST_F(V8ValueConverterImplTest, Prototype)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "(function() {"
                         "Object.prototype.foo = 'foo';"
                         "return {};"
                         "})();";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Object> object = script->Run().As<v8::Object>();
    ASSERT_FALSE(object.IsEmpty());

    V8ValueConverterImpl converter;
    std::unique_ptr<base::DictionaryValue> result(
        base::DictionaryValue::From(converter.FromV8Value(object, context)));
    ASSERT_TRUE(result.get());
    EXPECT_EQ(0u, result->size());
}

TEST_F(V8ValueConverterImplTest, ObjectPrototypeSetter)
{
    std::unique_ptr<base::Value> original = base::test::ParseJson("{ \"foo\": \"good value\" }");

    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(isolate_,
        v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "var result = { getters: 0, setters: 0 };"
                         "Object.defineProperty(Object.prototype, 'foo', {"
                         "  get() { ++result.getters; return 'bogus'; },"
                         "  set() { ++result.setters; },"
                         "});"
                         "result;";

    const char* source_sanity = "({}).foo = 'Trigger setter';"
                                "({}).foo;";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Object> result = script->Run().As<v8::Object>();
    ASSERT_FALSE(result.IsEmpty());

    // Sanity checks: the getters/setters are normally triggered.
    v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source_sanity))->Run();
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    V8ValueConverterImpl converter;
    v8::Local<v8::Object> converted = converter.ToV8Value(original.get(), context).As<v8::Object>();
    EXPECT_FALSE(converted.IsEmpty());

    // Getters/setters shouldn't be triggered.
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    EXPECT_EQ(1u, converted->GetPropertyNames()->Length());
    EXPECT_EQ("good value", GetString(converted, "foo"));

    // Getters/setters shouldn't be triggered while accessing existing values.
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    // Repeat the same exercise with a dictionary without the key.
    base::DictionaryValue missing_key_dict;
    missing_key_dict.SetString("otherkey", "hello");
    v8::Local<v8::Object> converted2 = converter.ToV8Value(&missing_key_dict, context).As<v8::Object>();
    EXPECT_FALSE(converted2.IsEmpty());

    // Getters/setters shouldn't be triggered.
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    EXPECT_EQ(1u, converted2->GetPropertyNames()->Length());
    EXPECT_EQ("hello", GetString(converted2, "otherkey"));

    // Missing key = should trigger getter upon access.
    EXPECT_EQ("bogus", GetString(converted2, "foo"));
    EXPECT_EQ(2, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));
}

TEST_F(V8ValueConverterImplTest, ArrayPrototypeSetter)
{
    std::unique_ptr<base::Value> original = base::test::ParseJson("[100, 200, 300]");

    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(isolate_,
        v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "var result = { getters: 0, setters: 0 };"
                         "Object.defineProperty(Array.prototype, '1', {"
                         "  get() { ++result.getters; return 1337; },"
                         "  set() { ++result.setters; },"
                         "});"
                         "result;";

    const char* source_sanity = "[][1] = 'Trigger setter';"
                                "[][1];";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Object> result = script->Run().As<v8::Object>();
    ASSERT_FALSE(result.IsEmpty());

    // Sanity checks: the getters/setters are normally triggered.
    v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source_sanity))->Run();
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    V8ValueConverterImpl converter;
    v8::Local<v8::Array> converted = converter.ToV8Value(original.get(), context).As<v8::Array>();
    EXPECT_FALSE(converted.IsEmpty());

    // Getters/setters shouldn't be triggered during the conversion.
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    EXPECT_EQ(3u, converted->Length());
    EXPECT_EQ(100, GetInt(converted, 0));
    EXPECT_EQ(200, GetInt(converted, 1));
    EXPECT_EQ(300, GetInt(converted, 2));

    // Getters/setters shouldn't be triggered while accessing existing values.
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    // Try again, using an array without the index.
    base::ListValue one_item_list;
    one_item_list.AppendInteger(123456);
    v8::Local<v8::Array> converted2 = converter.ToV8Value(&one_item_list, context).As<v8::Array>();
    EXPECT_FALSE(converted2.IsEmpty());

    // Getters/setters shouldn't be triggered during the conversion.
    EXPECT_EQ(1, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));

    EXPECT_EQ(1u, converted2->Length());
    EXPECT_EQ(123456, GetInt(converted2, 0));

    // Accessing missing index 1 triggers the getter.
    EXPECT_EQ(1337, GetInt(converted2, 1));
    EXPECT_EQ(2, GetInt(result, "getters"));
    EXPECT_EQ(1, GetInt(result, "setters"));
}

TEST_F(V8ValueConverterImplTest, StripNullFromObjects)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "(function() {"
                         "return { foo: undefined, bar: null };"
                         "})();";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Object> object = script->Run().As<v8::Object>();
    ASSERT_FALSE(object.IsEmpty());

    V8ValueConverterImpl converter;
    converter.SetStripNullFromObjects(true);

    std::unique_ptr<base::DictionaryValue> result(
        base::DictionaryValue::From(converter.FromV8Value(object, context)));
    ASSERT_TRUE(result.get());
    EXPECT_EQ(0u, result->size());
}

TEST_F(V8ValueConverterImplTest, RecursiveObjects)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    V8ValueConverterImpl converter;

    v8::Local<v8::Object> object = v8::Object::New(isolate_).As<v8::Object>();
    ASSERT_FALSE(object.IsEmpty());
    object->Set(v8::String::NewFromUtf8(isolate_, "foo"),
        v8::String::NewFromUtf8(isolate_, "bar"));
    object->Set(v8::String::NewFromUtf8(isolate_, "obj"), object);

    std::unique_ptr<base::DictionaryValue> object_result(
        base::DictionaryValue::From(converter.FromV8Value(object, context)));
    ASSERT_TRUE(object_result.get());
    EXPECT_EQ(2u, object_result->size());
    EXPECT_TRUE(IsNull(object_result.get(), "obj"));

    v8::Local<v8::Array> array = v8::Array::New(isolate_).As<v8::Array>();
    ASSERT_FALSE(array.IsEmpty());
    array->Set(0, v8::String::NewFromUtf8(isolate_, "1"));
    array->Set(1, array);

    std::unique_ptr<base::ListValue> list_result(
        base::ListValue::From(converter.FromV8Value(array, context)));
    ASSERT_TRUE(list_result.get());
    EXPECT_EQ(2u, list_result->GetSize());
    EXPECT_TRUE(IsNull(list_result.get(), 1));
}

TEST_F(V8ValueConverterImplTest, WeirdProperties)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "(function() {"
                         "return {"
                         "1: 'foo',"
                         "'2': 'bar',"
                         "true: 'baz',"
                         "false: 'qux',"
                         "null: 'quux',"
                         "undefined: 'oops'"
                         "};"
                         "})();";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Object> object = script->Run().As<v8::Object>();
    ASSERT_FALSE(object.IsEmpty());

    V8ValueConverterImpl converter;
    std::unique_ptr<base::Value> actual(converter.FromV8Value(object, context));

    std::unique_ptr<base::Value> expected = base::test::ParseJson(
        "{ \n"
        "  \"1\": \"foo\", \n"
        "  \"2\": \"bar\", \n"
        "  \"true\": \"baz\", \n"
        "  \"false\": \"qux\", \n"
        "  \"null\": \"quux\", \n"
        "  \"undefined\": \"oops\", \n"
        "}");

    EXPECT_TRUE(expected->Equals(actual.get()));
}

TEST_F(V8ValueConverterImplTest, ArrayGetters)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    const char* source = "(function() {"
                         "var a = [0];"
                         "a.__defineGetter__(1, function() { return 'bar'; });"
                         "return a;"
                         "})();";

    v8::Local<v8::Script> script(
        v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
    v8::Local<v8::Array> array = script->Run().As<v8::Array>();
    ASSERT_FALSE(array.IsEmpty());

    V8ValueConverterImpl converter;
    std::unique_ptr<base::ListValue> result(
        base::ListValue::From(converter.FromV8Value(array, context)));
    ASSERT_TRUE(result.get());
    EXPECT_EQ(2u, result->GetSize());
}

TEST_F(V8ValueConverterImplTest, UndefinedValueBehavior)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);

    v8::Local<v8::Object> object;
    {
        const char* source = "(function() {"
                             "return { foo: undefined, bar: null, baz: function(){} };"
                             "})();";
        v8::Local<v8::Script> script(
            v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
        object = script->Run().As<v8::Object>();
        ASSERT_FALSE(object.IsEmpty());
    }

    v8::Local<v8::Array> array;
    {
        const char* source = "(function() {"
                             "return [ undefined, null, function(){} ];"
                             "})();";
        v8::Local<v8::Script> script(
            v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
        array = script->Run().As<v8::Array>();
        ASSERT_FALSE(array.IsEmpty());
    }

    v8::Local<v8::Array> sparse_array;
    {
        const char* source = "(function() {"
                             "return new Array(3);"
                             "})();";
        v8::Local<v8::Script> script(
            v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
        sparse_array = script->Run().As<v8::Array>();
        ASSERT_FALSE(sparse_array.IsEmpty());
    }

    V8ValueConverterImpl converter;

    std::unique_ptr<base::Value> actual_object(
        converter.FromV8Value(object, context));
    EXPECT_TRUE(base::Value::Equals(
        base::test::ParseJson("{ \"bar\": null }").get(), actual_object.get()));

    // Everything is null because JSON stringification preserves array length.
    std::unique_ptr<base::Value> actual_array(
        converter.FromV8Value(array, context));
    EXPECT_TRUE(base::Value::Equals(
        base::test::ParseJson("[ null, null, null ]").get(), actual_array.get()));

    std::unique_ptr<base::Value> actual_sparse_array(
        converter.FromV8Value(sparse_array, context));
    EXPECT_TRUE(
        base::Value::Equals(base::test::ParseJson("[ null, null, null ]").get(),
            actual_sparse_array.get()));
}

TEST_F(V8ValueConverterImplTest, ObjectsWithClashingIdentityHash)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    V8ValueConverterImpl converter;

    // We check that the converter checks identity correctly by disabling the
    // optimization of using identity hashes.
    ScopedAvoidIdentityHashForTesting scoped_hash_avoider(&converter);

    // Create the v8::Object to be converted.
    v8::Local<v8::Array> root(v8::Array::New(isolate_, 4));
    root->Set(0, v8::Local<v8::Object>(v8::Object::New(isolate_)));
    root->Set(1, v8::Local<v8::Object>(v8::Object::New(isolate_)));
    root->Set(2, v8::Local<v8::Object>(v8::Array::New(isolate_, 0)));
    root->Set(3, v8::Local<v8::Object>(v8::Array::New(isolate_, 0)));

    // The expected base::Value result.
    std::unique_ptr<base::Value> expected = base::test::ParseJson("[{},{},[],[]]");
    ASSERT_TRUE(expected.get());

    // The actual result.
    std::unique_ptr<base::Value> value(converter.FromV8Value(root, context));
    ASSERT_TRUE(value.get());

    EXPECT_TRUE(expected->Equals(value.get()));
}

TEST_F(V8ValueConverterImplTest, DetectCycles)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    V8ValueConverterImpl converter;

    // Create a recursive array.
    v8::Local<v8::Array> recursive_array(v8::Array::New(isolate_, 1));
    recursive_array->Set(0, recursive_array);

    // The first repetition should be trimmed and replaced by a null value.
    base::ListValue expected_list;
    expected_list.Append(base::Value::CreateNullValue());

    // The actual result.
    std::unique_ptr<base::Value> actual_list(
        converter.FromV8Value(recursive_array, context));
    ASSERT_TRUE(actual_list.get());

    EXPECT_TRUE(expected_list.Equals(actual_list.get()));

    // Now create a recursive object
    const std::string key("key");
    v8::Local<v8::Object> recursive_object(v8::Object::New(isolate_));
    v8::TryCatch try_catch(isolate_);
    recursive_object->Set(
        v8::String::NewFromUtf8(
            isolate_, key.c_str(), v8::String::kNormalString, key.length()),
        recursive_object);
    ASSERT_FALSE(try_catch.HasCaught());

    // The first repetition should be trimmed and replaced by a null value.
    base::DictionaryValue expected_dictionary;
    expected_dictionary.Set(key, base::Value::CreateNullValue());

    // The actual result.
    std::unique_ptr<base::Value> actual_dictionary(
        converter.FromV8Value(recursive_object, context));
    ASSERT_TRUE(actual_dictionary.get());

    EXPECT_TRUE(expected_dictionary.Equals(actual_dictionary.get()));
}

// Tests that reused object values with no cycles do not get nullified.
TEST_F(V8ValueConverterImplTest, ReuseObjects)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);
    v8::MicrotasksScope microtasks(
        isolate_, v8::MicrotasksScope::kDoNotRunMicrotasks);
    V8ValueConverterImpl converter;

    // Object with reused values in different keys.
    {
        const char* source = "(function() {"
                             "var objA = {key: 'another same value'};"
                             "var obj = {one: objA, two: objA};"
                             "return obj;"
                             "})();";
        v8::Local<v8::Script> script(
            v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
        v8::Local<v8::Object> object = script->Run().As<v8::Object>();
        ASSERT_FALSE(object.IsEmpty());

        // The actual result.
        std::unique_ptr<base::DictionaryValue> result(
            base::DictionaryValue::From(converter.FromV8Value(object, context)));
        ASSERT_TRUE(result.get());
        EXPECT_EQ(2u, result->size());

        {
            base::DictionaryValue* one_dict = nullptr;
            const char* key1 = "one";
            ASSERT_TRUE(result->GetDictionary(key1, &one_dict));
            EXPECT_EQ("another same value", GetString(one_dict, "key"));
        }
        {
            base::DictionaryValue* two_dict = nullptr;
            const char* key2 = "two";
            ASSERT_TRUE(result->GetDictionary(key2, &two_dict));
            EXPECT_EQ("another same value", GetString(two_dict, "key"));
        }
    }

    // Array with reused values.
    {
        const char* source = "(function() {"
                             "var objA = {key: 'same value'};"
                             "var arr = [objA, objA];"
                             "return arr;"
                             "})();";
        v8::Local<v8::Script> script(
            v8::Script::Compile(v8::String::NewFromUtf8(isolate_, source)));
        v8::Local<v8::Array> array = script->Run().As<v8::Array>();
        ASSERT_FALSE(array.IsEmpty());

        // The actual result.
        std::unique_ptr<base::ListValue> list_result(
            base::ListValue::From(converter.FromV8Value(array, context)));
        ASSERT_TRUE(list_result.get());
        ASSERT_EQ(2u, list_result->GetSize());
        for (size_t i = 0; i < list_result->GetSize(); ++i) {
            ASSERT_FALSE(IsNull(list_result.get(), i));
            base::DictionaryValue* dict_value = nullptr;
            ASSERT_TRUE(list_result->GetDictionary(0u, &dict_value));
            EXPECT_EQ("same value", GetString(dict_value, "key"));
        }
    }
}

TEST_F(V8ValueConverterImplTest, MaxRecursionDepth)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    // Must larger than kMaxRecursionDepth in v8_value_converter_impl.cc.
    int kDepth = 1000;
    const char kKey[] = "key";

    v8::Local<v8::Object> deep_object = v8::Object::New(isolate_);

    v8::Local<v8::Object> leaf = deep_object;
    for (int i = 0; i < kDepth; ++i) {
        v8::Local<v8::Object> new_object = v8::Object::New(isolate_);
        leaf->Set(v8::String::NewFromUtf8(isolate_, kKey), new_object);
        leaf = new_object;
    }

    V8ValueConverterImpl converter;
    std::unique_ptr<base::Value> value(
        converter.FromV8Value(deep_object, context));
    ASSERT_TRUE(value);

    // Expected depth is kMaxRecursionDepth in v8_value_converter_impl.cc.
    int kExpectedDepth = 100;

    base::Value* current = value.get();
    for (int i = 1; i < kExpectedDepth; ++i) {
        base::DictionaryValue* current_as_object = NULL;
        ASSERT_TRUE(current->GetAsDictionary(&current_as_object)) << i;
        ASSERT_TRUE(current_as_object->Get(kKey, &current)) << i;
    }

    // The leaf node shouldn't have any properties.
    base::DictionaryValue empty;
    EXPECT_TRUE(base::Value::Equals(&empty, current)) << *current;
}

class V8ValueConverterOverridingStrategyForTesting
    : public V8ValueConverter::Strategy {
public:
    V8ValueConverterOverridingStrategyForTesting()
        : reference_value_(NewReferenceValue())
    {
    }
    bool FromV8Object(v8::Local<v8::Object> value,
        std::unique_ptr<base::Value>* out,
        v8::Isolate* isolate,
        const FromV8ValueCallback& callback) const override
    {
        *out = NewReferenceValue();
        return true;
    }
    bool FromV8Array(v8::Local<v8::Array> value,
        std::unique_ptr<base::Value>* out,
        v8::Isolate* isolate,
        const FromV8ValueCallback& callback) const override
    {
        *out = NewReferenceValue();
        return true;
    }
    bool FromV8ArrayBuffer(v8::Local<v8::Object> value,
        std::unique_ptr<base::Value>* out,
        v8::Isolate* isolate) const override
    {
        *out = NewReferenceValue();
        return true;
    }
    bool FromV8Number(v8::Local<v8::Number> value,
        std::unique_ptr<base::Value>* out) const override
    {
        *out = NewReferenceValue();
        return true;
    }
    bool FromV8Undefined(std::unique_ptr<base::Value>* out) const override
    {
        *out = NewReferenceValue();
        return true;
    }
    base::Value* reference_value() const { return reference_value_.get(); }

private:
    static std::unique_ptr<base::Value> NewReferenceValue()
    {
        return base::MakeUnique<base::StringValue>("strategy");
    }
    std::unique_ptr<base::Value> reference_value_;
};

TEST_F(V8ValueConverterImplTest, StrategyOverrides)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    V8ValueConverterImpl converter;
    V8ValueConverterOverridingStrategyForTesting strategy;
    converter.SetStrategy(&strategy);

    v8::Local<v8::Object> object(v8::Object::New(isolate_));
    std::unique_ptr<base::Value> object_value(
        converter.FromV8Value(object, context));
    ASSERT_TRUE(object_value);
    EXPECT_TRUE(
        base::Value::Equals(strategy.reference_value(), object_value.get()));

    v8::Local<v8::Array> array(v8::Array::New(isolate_));
    std::unique_ptr<base::Value> array_value(
        converter.FromV8Value(array, context));
    ASSERT_TRUE(array_value);
    EXPECT_TRUE(
        base::Value::Equals(strategy.reference_value(), array_value.get()));

    v8::Local<v8::ArrayBuffer> array_buffer(v8::ArrayBuffer::New(isolate_, 0));
    std::unique_ptr<base::Value> array_buffer_value(
        converter.FromV8Value(array_buffer, context));
    ASSERT_TRUE(array_buffer_value);
    EXPECT_TRUE(base::Value::Equals(strategy.reference_value(),
        array_buffer_value.get()));

    v8::Local<v8::ArrayBufferView> array_buffer_view(
        v8::Uint8Array::New(array_buffer, 0, 0));
    std::unique_ptr<base::Value> array_buffer_view_value(
        converter.FromV8Value(array_buffer_view, context));
    ASSERT_TRUE(array_buffer_view_value);
    EXPECT_TRUE(base::Value::Equals(strategy.reference_value(),
        array_buffer_view_value.get()));

    v8::Local<v8::Number> number(v8::Number::New(isolate_, 0.0));
    std::unique_ptr<base::Value> number_value(
        converter.FromV8Value(number, context));
    ASSERT_TRUE(number_value);
    EXPECT_TRUE(
        base::Value::Equals(strategy.reference_value(), number_value.get()));

    v8::Local<v8::Primitive> undefined(v8::Undefined(isolate_));
    std::unique_ptr<base::Value> undefined_value(
        converter.FromV8Value(undefined, context));
    ASSERT_TRUE(undefined_value);
    EXPECT_TRUE(
        base::Value::Equals(strategy.reference_value(), undefined_value.get()));
}

class V8ValueConverterBypassStrategyForTesting
    : public V8ValueConverter::Strategy {
public:
    bool FromV8Object(v8::Local<v8::Object> value,
        std::unique_ptr<base::Value>* out,
        v8::Isolate* isolate,
        const FromV8ValueCallback& callback) const override
    {
        return false;
    }
    bool FromV8Array(v8::Local<v8::Array> value,
        std::unique_ptr<base::Value>* out,
        v8::Isolate* isolate,
        const FromV8ValueCallback& callback) const override
    {
        return false;
    }
    bool FromV8ArrayBuffer(v8::Local<v8::Object> value,
        std::unique_ptr<base::Value>* out,
        v8::Isolate* isolate) const override
    {
        return false;
    }
    bool FromV8Number(v8::Local<v8::Number> value,
        std::unique_ptr<base::Value>* out) const override
    {
        return false;
    }
    bool FromV8Undefined(std::unique_ptr<base::Value>* out) const override
    {
        return false;
    }
};

// Verify that having a strategy that fallbacks to default behaviour
// actually preserves it.
TEST_F(V8ValueConverterImplTest, StrategyBypass)
{
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);
    v8::Context::Scope context_scope(context);

    V8ValueConverterImpl converter;
    V8ValueConverterBypassStrategyForTesting strategy;
    converter.SetStrategy(&strategy);

    v8::Local<v8::Object> object(v8::Object::New(isolate_));
    std::unique_ptr<base::Value> object_value(
        converter.FromV8Value(object, context));
    ASSERT_TRUE(object_value);
    std::unique_ptr<base::Value> reference_object_value(
        base::test::ParseJson("{}"));
    EXPECT_TRUE(
        base::Value::Equals(reference_object_value.get(), object_value.get()));

    v8::Local<v8::Array> array(v8::Array::New(isolate_));
    std::unique_ptr<base::Value> array_value(
        converter.FromV8Value(array, context));
    ASSERT_TRUE(array_value);
    std::unique_ptr<base::Value> reference_array_value(
        base::test::ParseJson("[]"));
    EXPECT_TRUE(
        base::Value::Equals(reference_array_value.get(), array_value.get()));

    // Not testing ArrayBuffers as V8ValueConverter uses blink helpers and
    // this requires having blink to be initialized.

    v8::Local<v8::Number> number(v8::Number::New(isolate_, 0.0));
    std::unique_ptr<base::Value> number_value(
        converter.FromV8Value(number, context));
    ASSERT_TRUE(number_value);
    std::unique_ptr<base::Value> reference_number_value(
        base::test::ParseJson("0"));
    EXPECT_TRUE(
        base::Value::Equals(reference_number_value.get(), number_value.get()));

    v8::Local<v8::Primitive> undefined(v8::Undefined(isolate_));
    std::unique_ptr<base::Value> undefined_value(
        converter.FromV8Value(undefined, context));
    EXPECT_FALSE(undefined_value);
}

} // namespace content
